package dpss.commons;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.UUID;

/**
 * The server to server UDP communication message
 * 
 * @author musab.mirza
 * 
 */
public class UDPMessage implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private String message;
	private Object data;
	private boolean success;
	private String errorMessage;
	private static InetSocketAddress senderSocketAddress;

	// Used for DPSS request and response
	private UUID requestId = null;
	private UUID responseId = null;

	// The byte size defined for this type
	public static final Integer BYTE_SIZE = 1024;

	/**
	 * Init with data and details
	 * 
	 * @param message
	 * @param data
	 * @param success
	 * @param errorMessage
	 */
	public UDPMessage(String message, Object data, boolean success,
			String errorMessage) {
		this.message = message;
		this.data = data;
		this.success = success;
		this.errorMessage = errorMessage;
	}

	public UUID getRequestId() {
		return requestId;
	}

	public void setRequestId(UUID requestId) {
		this.requestId = requestId;
	}

	public UUID getResponseId() {
		return responseId;
	}

	public void setResponseId(UUID responseId) {
		this.responseId = responseId;
	}

	/**
	 * @return the message
	 */
	public String getMessage() {
		return message;
	}

	/**
	 * @param message
	 *            the message to set
	 */
	public void setMessage(String message) {
		this.message = message;
	}

	/**
	 * @return the data
	 */
	public Object getData() {
		return data;
	}

	/**
	 * @param data
	 *            the data to set
	 */
	public void setData(Object data) {
		this.data = data;
	}

	/**
	 * Convert this object to bytes for transfer
	 * 
	 * @return
	 */
	public byte[] getBytes() {
		ByteArrayOutputStream bStream = new ByteArrayOutputStream();
		ObjectOutput oo = null;
		try {
			oo = new ObjectOutputStream(bStream);
			oo.writeObject(this);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return bStream.toByteArray();
	}

	/**
	 * Convert from bytes to a UDP message
	 * 
	 * @param bytes
	 * @return
	 */
	public static UDPMessage getFromBytes(byte[] bytes) {
		UDPMessage message = null;
		ObjectInputStream iStream;
		try {
			iStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
			message = (UDPMessage) iStream.readObject();
			iStream.close();
		} catch (IOException | ClassNotFoundException e1) {
			e1.printStackTrace();
		}
		return message;
	}

	/**
	 * @return the errorMessage
	 */
	public String getErrorMessage() {
		return errorMessage;
	}

	/**
	 * @param errorMessage
	 *            the errorMessage to set
	 */
	public void setErrorMessage(String errorMessage) {
		this.errorMessage = errorMessage;
	}

	/**
	 * @return the success
	 */
	public boolean isSuccess() {
		return success;
	}

	/**
	 * @param success
	 *            the success to set
	 */
	public void setSuccess(boolean success) {
		this.success = success;
	}

	public InetSocketAddress getSenderSocketAddress() {
		return senderSocketAddress;
	}

	/**
	 * Sends a message to
	 * 
	 * @param socket
	 * @param messageToSend
	 * @param ip
	 * @param port
	 * @throws IOException
	 */
	public static void sendMessage(DatagramSocket socket,
			UDPMessage messageToSend, InetSocketAddress socketAddress) {
		try {
			DatagramPacket packetToSend = new DatagramPacket(
					messageToSend.getBytes(), messageToSend.getBytes().length,
					socketAddress);
			socket.send(packetToSend);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Receives a UDP message from a datagram socket, appends the sender's IP
	 * and PORT info and returns it as a UDPMessage
	 * 
	 * @param socket
	 *            Socket to listen to
	 * @return
	 */
	public static UDPMessage receiveUDPMessage(DatagramSocket socket) {
		try {
			DatagramPacket receivedPacket = new DatagramPacket(
					new byte[UDPMessage.BYTE_SIZE], UDPMessage.BYTE_SIZE);
			socket.receive(receivedPacket);
			UDPMessage receivedMessage = UDPMessage.getFromBytes(receivedPacket
					.getData());

			InetSocketAddress senderSocketAddress = new InetSocketAddress(
					receivedPacket.getAddress(), receivedPacket.getPort());
			receivedMessage.setSenderSocketAddress(senderSocketAddress);
			return receivedMessage;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	private void setSenderSocketAddress(InetSocketAddress senderSocketAddress) {
		this.senderSocketAddress = senderSocketAddress;
	}
	
	/**
	 * The toString
	 */
	public String toString(){
		String toReturn = "UDPMessage: [";
		if (message != null && !message.isEmpty())
			toReturn += "message: " + message;
		if (senderSocketAddress != null)
			toReturn += ", sender: " + senderSocketAddress.toString();
		if (data!=null)
			toReturn +=", data(type): " + data.getClass().toString();
		toReturn +=", success: " + success;
		if (errorMessage!=null && !errorMessage.isEmpty())
			toReturn +=", errorMessage: " + errorMessage;
		if (requestId!=null)
			toReturn +=", requestId: " + requestId.toString();
		if (responseId!=null)
			toReturn +=", responseId: " + responseId.toString();
		return toReturn + "]";
	}

}
